import type { Logger, MaySuccess } from '@gnlab/utils'
import { getLogger, Service, tuple } from '@gnlab/utils'
import { MemSubject } from '@gnlab/utils/rx'
import { OrderedMap } from 'immutable'
import { Subject } from 'rxjs'
import type { Services } from '@/services'
import { type ChatRecord } from '@/services/contact-chat'
import { compareContacts } from '@/services/contacts'
import type { BaseContact, Contact } from '@/services/contacts'

export interface RawLoadInfoConditions {
  id: BaseContact['id']
  name?: string
}
export interface LoadInfoConditions extends RawLoadInfoConditions {
  ownerWxid: BaseContact['ownerWxid']
  contactUserId: BaseContact['userId']

  // 若是因收到聊天消息而加载联系人信息，此为对应的消息。
  // 因为调接口获取到的“最近联系时间”和“最近聊天消息”可能不是最新的，此时就要用这条消息代替接口返回的。
  byChatRecord?: ChatRecord
}

export abstract class ContactTypeService<
  T extends Contact,
  ContactEvent = unknown,
> extends Service<Services> {
  protected get qywxs() {
    return this.services.qywxs
  }

  protected get tops() {
    return this.services.contacts.tops.items.value
  }

  protected readonly logger: Logger

  // 列表内容会按“最近联系时间”排序
  protected readonly _list = new MemSubject(OrderedMap<string, T>())
  readonly list = this._list.asStream()

  // 推送列表事件
  protected readonly _added = new Subject<T[]>()
  readonly added = this._added.asObservable() // 有新联系人添加进列表
  protected readonly _removed = new Subject<T[]>()
  readonly removed = this._removed.asObservable() // 有联系人被移除

  // 推送此类联系人的自定义事件
  protected readonly _events = new Subject<ContactEvent>()
  readonly events = this._events.asObservable()

  // 标记“加载中”状态（包括“企微加载”和“分页加载”）
  loading: null | Promise<MaySuccess<T[]>> = null

  // 推送“加载中”状态
  private readonly loading$$ = new MemSubject(false)
  readonly loading$ = this.loading$$.asStream()

  // 对于分页加载，是否已完全加载
  fullLoaded = false

  // 单次加载的联系人数量（“企微加载”和“分页加载”都用此数量）
  get pageSize() {
    return 100
  }

  constructor(name: string) {
    super()
    this.logger = getLogger(`services/${name}`)
  }

  registered() {
    this.initSyncWithQywx()
    this.initEventEmitter()
  }

  /**
   * ============================================
   * 维护联系人列表
   * ============================================
   */

  protected initSyncWithQywx() {
    // 发现新企业微信时，触发同步联系人
    this.qywxs.bufferPrepared.subscribe(async infos =>
      this.loadForQywxs(infos.map(info => info.wxid)),
    )

    // 企业微信移除时，清理联系人列表
    this.qywxs.notAlived.subscribe(info => this.clearForQywx(info.wxid))
  }

  /**
   * 加载指定企微的联系人并合并入列表
   * （仅供上级 Service 使用）
   */
  async loadForQywxs(wxids: string[]) {
    const request = this.request({ wxids, page: 1, pageSize: this.pageSize })
    this.markLoading(request)
    const res = await request
    if (this.loading === request) this.markLoading(null)
    if (res.success) this.mergeContacts(res.data)
    else void this.logger.warn('拉取指定企微的联系人失败', res.error, { wxids })
  }

  /**
   * 分页加载 - 加载下一页
   */
  async loadNext() {
    if (this.fullLoaded) return
    const loaded = this.list.value.size // 已加载多少个联系人
    const conditions = {
      wxids: [...this.qywxs.alives.value.values()].map(v => v.wxid),
      page: Math.floor(loaded / this.pageSize) + 1,
      pageSize: this.pageSize,
    }
    void this.logger.debug('加载下一页联系人', conditions)
    const request = this.request(conditions)
    this.markLoading(request)
    const res = await request
    if (this.loading === request) this.markLoading(null)
    if (res.success) {
      // 返回数据量小于 pageSize 视为已完整加载
      if (res.data.length < this.pageSize) this.fullLoaded = true
      if (res.data.length) this.mergeContacts(res.data)
    } else {
      void this.logger.warn('加载下一页联系人失败', res.error, conditions)
    }
  }

  protected markLoading(loading: null | Promise<MaySuccess<T[]>>) {
    this.loading = loading
    this.loading$$.next(loading !== null)
  }

  /**
   * 执行加载行为，需子类实现
   */
  abstract request(conditions: {
    wxids: string[]
    page: number
    pageSize: number
  }): Promise<MaySuccess<T[]>>

  /**
   * 把新加载到的联系人和并入列表
   */
  protected mergeContacts(newContacts: T[]) {
    const contacts = this.sortContacts(
      OrderedMap([...this.list.value, ...newContacts.map(v => tuple(v.id, v))]),
    )
    this._list.next(contacts)
    this._added.next(newContacts) // 推送事件
  }

  /**
   * 整理联系人数组（排序、过滤）
   */
  protected sortContacts<T extends Contact>(items: OrderedMap<string, T>) {
    return items
      .filter(contact => this.qywxs.usables.value.has(contact.ownerWxid))
      .sort(compareContacts)
  }

  /**
   * 按指定条件清理联系人
   * shouldClear 返回 true 的清理；返回 false 的保留
   */
  protected clear(shouldClear: (contact: T) => boolean) {
    const removed: T[] = []
    const next = this.list.value.filter(contact => {
      if (shouldClear(contact)) {
        removed.push(contact)
        return false
      }
      return true
    })
    this._list.next(next)
    this._removed.next(removed) // 推送事件
  }

  protected clearForQywx(wxid: string) {
    this.clear(contact => contact.ownerWxid === wxid)
  }

  /**
   * 清空列表并重新触发加载（例如加载所需的筛选条件发生变化）
   */
  protected reload() {
    // 保留置顶列表里的联系人，因为它们是联系人列表加载完成后，由 tops 模块手动补充过的。
    // 如果清理了，又没有重新触发补充，就丢失了。
    this.clear(contact => !this.tops.has(contact.id))

    this.fullLoaded = false
    void this.loadNext()
  }

  /**
   * ============================================
   * 加载、更新指定联系人资料
   * ============================================
   */

  /**
   * 尝试根据提供的片段加载此类型的联系人
   * 成功返回 true，失败返回 false，判定为不是此类型的联系人，返回 null
   *
   * 仅供上级 Service 调用
   */
  abstract tryLoadInfo(conditions: LoadInfoConditions): Promise<boolean | null> | boolean | null

  /**
   * 新增或更新联系人信息
   *
   * - 仅供上级 Service 调用
   *
   * - 若 goTop=true，会同时将联系人提升到列表最前面（如果是新增，固定会将联系人加入到列表最前面）。
   *   注意：目前除了更新 contacted 属性外，应该没有其他情况需要设置 goTop=true
   */
  listAddOrUpdate(inputContacts: T | T[], goTop = false) {
    const contacts = Array.isArray(inputContacts) ? inputContacts : [inputContacts]
    const added: T[] = [] // 原列表里没有的联系人
    const inplaces: T[] = [] // 在原位置更新的联系人
    const prepends: T[] = [] // 要添加到列表开头的联系人
    for (const contact of contacts) {
      if (!this.list.value.has(contact.id)) {
        added.push(contact)
        prepends.unshift(contact)
      } else {
        if (goTop) prepends.unshift(contact)
        else inplaces.push(contact)
      }
    }

    let list = this.list.value
    if (inplaces.length) list = list.concat(inplaces.map(contact => [contact.id, contact]))
    if (prepends.length) {
      list = OrderedMap(prepends.map(contact => [contact.id, contact])).concat(
        list.deleteAll(prepends.map(contact => contact.id)),
      )
    }
    this._list.next(list)

    if (added.length) this._added.next(added) // 推送事件
  }

  /**
   * 把指定联系人移动到列表最前面（联系人必须已经在列表里）
   */
  goTop(contactId: string) {
    const contact = this.list.value.get(contactId)
    if (contact) {
      // 更新 contacted 保证后续有联系人加载进来时，它经过排序还是能在前面
      this.listAddOrUpdate({ ...contact, contacted: Date.now() })
    } else {
      void this.logger.warn(`找不到联系人 ${contactId} 无法将其移到最前面`)
    }
  }

  /**
   * ============================================
   * 自定义事件
   * ============================================
   */

  /**
   * 初始化事件推送
   * 默认实现为空，子类根据需要填充内容
   */
  initEventEmitter() {}
}
